//测试CookTorrance的高光反射

#include "Utils.hlsl"

//顶点着色器输出
struct VS_OUTPUT
{
	float4 Pos : SV_POSITION;
	float3 Normal : NORMAL;
	float3 Tangent : TANGENT;
	float2 TexCoord : TEXCOORD0;
	float3 WorldPosition : TEXCOORD1;
};

Texture2D albedoTexture : register(t1);
Texture2D metallicTexture : register(t2);
Texture2D roughnessTexture : register(t3);
Texture2D normalTexture : register(t4);
TextureCube skyboxCubeMap : register( t9 );
TextureCube skyboxDiffuseCubeMap : register(t8);
SamplerState ourSampler : register( s5 );

cbuffer SceneData : register( b7 )
{
	matrix View;
	matrix Projection;
	float4 lightDir;
	float4 eyePosition;
	float4 TestValue;
}

//GGX采样微面元法线
//sampleNum：采样数目，用于产生随机值
//sampleCount：采样总数
//a：GGX中的粗糙度
//N：宏观表面的法线
float3 GGXsampleMicroNormal(int sampleNum, int sampleCount, float a, float3 N)
{
	//使用Hammersley生成随机值：
	float2 rand = Hammersley(sampleNum, sampleCount);
	//公式中输入的两个随机值：
	float xi_1 = rand.x;
	float xi_2 = rand.y;

	//使用公式生成极角：
	float theta = atan(a * sqrt(xi_1) / sqrt(1 - xi_1));
	//使用公式生成方位角：
	float phi = 2 * PI * xi_2;

	//生成一个任意的以法线为Z轴的坐标系（XY轴的朝向是无所谓的，因为phi在2PI内均匀随机）
	float3 NotN = abs(N.z) < 0.999 ? float3(0.0, 0.0, 1.0) : float3(1.0, 0.0, 0.0);//任意一个不是N的方向
	float3 T = normalize(cross(NotN, N));	//叉乘得到切线，一定和法线垂直
	float3 B = cross(N, T);		//叉乘得到副法线，一定和法线以及切线垂直

	//将方向变换到世界空间：
	return T * (sin(theta) * cos(phi)) +
		B * (sin(theta) * sin(phi)) +
		N * cos(theta);
}

//GGX的几何函数
//a：粗糙度
//w：要检查的方向（入射或出射）
//m：微面元的法线
//n：宏观表面的法线
float GGX_Geometry(float a, float3 w, float3 m, float3 n)
{
	float w_d_m = dot(w, m);
	float w_d_n = dot(w, n);
	float tanTheta2 = (1.0 - pow(w_d_n, 2)) / pow(w_d_n, 2);
	return chi(w_d_m / w_d_n) * 2 /
		(1 + sqrt(1 + a * a * tanTheta2));
}

//Schlick的Fresnel：
float3 Fresnel_Schlick(float3 F0, float3 normal, float3 view)
{
	float cosTheta = dot(normalize(view), normalize(normal));
	return F0 + (float3(1, 1, 1) - F0) * pow(1 - cosTheta, 5);
}

//计算CookTorrance的高光
//normal：宏观表面的法线朝向
//view：视线方向
//roughness：表面粗糙度
//FO：0°的菲涅尔值
//kS：高光部分占总体光的百分比，此值在此函数中计算得到
float3 CookTorranceSpecular(float3 normal, float3 view, float roughness, float3 F0, out float3 kS)
{
	float3 radiance;//最终的高光结果。会进行累加然后平均化

	int sampleCount = 100;	//采样次数
	for (int i = 0; i < sampleCount; i++)
	{
		//当前采样中的微面元的法线方向：
		float3 MicroNormal = GGXsampleMicroNormal(i, sampleCount, roughness, normal);
		//反射光方向（或者说来自此方向上的光将会反射到视线方向上）
		float3 lightDir = -reflect(view, MicroNormal);

		//计算一些点乘的值方便后续运算：
		float NoV = saturate(dot(normal, view));
		float NoL = saturate(dot(normal, lightDir));
		float NoH = saturate(dot(normal, MicroNormal));
		float VoH = saturate(dot(view, MicroNormal));
		if (NoL > 0)
		{
			//计算菲涅尔
			float3 F = Fresnel_Schlick(F0, normal, view);
			//累加统计高光系数
			kS += F;

			//计算几何函数
			float G = GGX_Geometry(roughness, view, MicroNormal, normal)
				* GGX_Geometry(roughness, lightDir, MicroNormal, normal);

			//采样环境光CubeMap
			float3 SampleColor = skyboxCubeMap.Sample(ourSampler, lightDir).rgb;
			
			// Incident light = SampleColor * NoL
			// Microfacet specular = D*G*F / (4*NoL*NoV)
			// pdf = D * NoH / (4 * VoH)
			radiance += SampleColor * F * G * VoH / (NoH * NoV);
		}
	}

	//平均高光与高光系数：
	kS /= sampleCount;
	radiance /= sampleCount;
	
	return radiance;
}

float4 Main(VS_OUTPUT input) : SV_Target
{
	//绝缘体的固定F0
	const float F0dielectrics = float3(0.1, 0.1, 0.1);
	
	//uv：
	float2 uv = input.TexCoord ;
	
	//Albedo：从贴图采样
	float3 Albedo = albedoTexture.Sample(ourSampler, uv).rgb;
	//金属性：从贴图采样
	float Metallic = metallicTexture.Sample(ourSampler, uv).r;
	//粗糙度：从贴图采样
	float Roughness = roughnessTexture.Sample(ourSampler, uv).r;
	//最终的F0：根据金属性来插值
	float3 F0 = lerp(F0dielectrics, Albedo, Metallic);
	//基础色：当前等于Albedo
	float3 BaseColor = Albedo;
	
	//--------------------------------------------------------
	//采样法线贴图计算法线方向：
	float3 NormalDir = normalize(input.Normal);
	{
		//切线空间的法线
		float4 TangentNormal = normalTexture.Sample(ourSampler, uv);
		TangentNormal = TangentNormal * 2 - float4(1, 1, 1, 1);

		//切线空间三轴在模型空间的基向量：
		float3 BaseN = normalize(input.Normal);
		float3 BaseT = normalize(input.Tangent);
		float3 BaseB = cross(BaseT, BaseN);

		//模型空间的法线方向：
		NormalDir = BaseT * TangentNormal.x + BaseB * TangentNormal.y + BaseN * TangentNormal.z;
	}
	//--------------------------------------------------------

	//视线方向
	float3 V = normalize(eyePosition.xyz - input.WorldPosition);

	//高光系数（会被之后计算）
	float3 kS = 0;
	//高光颜色由
	float3 SpecularColor = CookTorranceSpecular(NormalDir, V, Roughness, F0, kS);

	//漫反射系数：
	float3 kD = (1 - kS);
	//漫反射颜色：
	float3 DiffuseColor = skyboxDiffuseCubeMap.Sample(ourSampler, NormalDir).rgb * BaseColor;
	
	//输出最终颜色！
	return float4(DiffuseColor * kD + SpecularColor, 1);
}
